﻿using System.Collections.Generic;
using System.Net.Sockets;

//------------------------------------------------------------
// shaco Framework
// Copyright © 2017-2021 chang.liu All rights reserved.
// Feedback: 449612236@qq.com
//------------------------------------------------------------

namespace shaco.Base.Net
{
    /// <summary>
    /// 网络套接字，主要负责网络数据传输使用
    /// </summary>
    public class ClientTCP : IClient
    {
        // private enum SocketStatus
        // {
        //     None,
        //     ConnectRequest,
        //     Connecting,
        //     ConnectingEnd,
        //     Connected,
        //     Reconnecting,
        //     Disconnect
        // }

        /// <summary>
        /// 连接网络超时回调时间
        /// </summary>
        public float connectTimeoutSeconds { get { return _connectTimeoutSeconds; } }
        private float _connectTimeoutSeconds = 10.0f;

        /// <summary>
        /// 发送消息网络超时回调时间
        /// </summary>
        public float sendTimeoutSeconds { get { return _sendTimeoutSeconds; } }
        private float _sendTimeoutSeconds = 30.0f;

        /// <summary>
        /// 接收消息网络超时回调时间
        /// </summary>
        public float receiveTimeoutSeconds { get { return _receiveTimeoutSeconds; } }
        private float _receiveTimeoutSeconds = 30.0f;

        /// <summary>
        /// 最大网络重联次数
        /// </summary>
        public int maxReconnectTimes { get { return _maxReconnectTimes; } }
        private int _maxReconnectTimes = 5;

        /// <summary>
        /// 当前重连次数
        /// </summary>
        private int _currentReconnectTimes = 0;

        /// <summary>
        /// 接收信息头长度
        /// </summary>
        private int _receiveHeaderLength = 0;

        /// <summary>
        /// 接收数据回调
        /// </summary>
        private EventCallBack<ReceiveEventInfo> _callbackReceiveData = null;

        /// <summary>
        /// 发送数据结果回调
        /// </summary>
        private EventCallBack<SendEventInfo> _callbackSendDataResult = null;

        /// <summary>
        /// 重连回调
        /// </summary>
        private EventCallBack<System.Action<bool>> _callbackReconnect = null;

        /// <summary>
        /// 断线回调
        /// </summary>
        private EventCallBack _callbackDisconnect = null;

        /// <summary>
        /// 接收信息头解析方法
        /// </summary>
        private System.Func<byte[], IHeaderInfo> _callbackReceiveHeaderParse = null;

        /// <summary>
        /// 当前使用的连接数据
        /// </summary>
        private ISocket _socket = null;

        /// <summary>
        /// 接收数据缓存
        /// </summary>
        private byte[] _receiveBuffer = null;

        /// <summary>
        /// 数据缓存对象
        /// </summary>
        private DataBuffer _dataBuffer = null;

        /// <summary>
        /// 本次接收的信息头数据
        /// </summary>
        private IHeaderInfo _currentReceiveHeaderInfo = null;

        /// <summary>
        /// 是否已经开启过接收数据线程
        /// </summary>
        private bool _isThreadReceiveStarted = false;

        /// <summary>
        /// 数据接收回调信息
        /// </summary>
        private System.Collections.Generic.Queue<ReceiveEventInfo> _queueReceiveEvents = new System.Collections.Generic.Queue<ReceiveEventInfo>();

        // /// <summary>
        // /// 套接字状态
        // /// </summary>
        // private SocketStatus _sockectStatus = SocketStatus.None;

        /// <summary>
        /// 连接网络
        /// <param name="host">主机地址</param>
        /// <param name="port">主机端口号</param>
        /// <param name="callback">连接结果回调(true:成功 false:失败)</param>
        /// </summary>
        public void Connect(string host, int port, System.Action<bool> callback)
        {
            ConnectBase(host, port, (success) =>
            {
                if (success)
                {
                    Log.Info("ClientTCP Connect success, host=" + host + " port=" + port);

                    _receiveBuffer = new byte[1024 * 1024];
                    _dataBuffer = new DataBuffer();
                }
                else
                {
                    Log.Error("ClientTCP Connect failed");
                    Disconnect();
                }
                if (success && !_isThreadReceiveStarted)
                {
                    _isThreadReceiveStarted = true;
                    StartReceiveData();
                }

                if (null != callback)
                    callback(success);
            });
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void Disconnect()
        {
            _receiveBuffer = null;
            if (null != _dataBuffer)
            {
                _dataBuffer.Close();
                _dataBuffer = null;
            }

            if (null == _socket)
            {
                Log.Error("ClientTCP Disconnect error: socket is not connected");
            }
            else
            {
                Log.Info("ClientTCP Disconnected");

                _socket.Disconnect();
                _socket = null;
            }

            _isThreadReceiveStarted = false;
            _callbackReceiveData = null;
            _callbackSendDataResult = null;
            _callbackReconnect = null;

            if (null != _callbackDisconnect)
            {
                _callbackDisconnect.InvokeAllCallBack();
                _callbackDisconnect = null;
            }
        }

        /// <summary>
        /// 发送数据
        /// <param name="data">二进制数据</param>
        /// </summary>
        public void Send(byte[] data)
        {
            if (null == _socket || !_socket.IsConnected())
            {
                Log.Error("ClientTCP Send erorr: sockect is not connect");
                return;
            }

            try
            {
                var result = _socket.Send(data);

                if (result >= 0)
                    SafeInvokeSendEventInfo(data, string.Empty);
                else
                    SafeInvokeSendEventInfo(null, "ClientTCP Send error: send nothing");
            }
            catch (System.Exception e)
            {
                SafeInvokeSendEventInfo(null, "ClientTCP Send exception: e=" + e + " data=" + data.ToStringArray());
            }
        }

        /// <summary>
        /// 设置超时时长
        /// <param name="connect">连接超时时间(单位：秒)</param>
        /// <param name="send">发送超时时间(单位：秒)</param>
        /// <param name="receive">接收超时时间(单位：秒)</param>
        /// </summary>
        public void SetTimeoutSeconds(float connect, float send, float receive)
        {
            _connectTimeoutSeconds = connect;
            _sendTimeoutSeconds = send;
            _receiveTimeoutSeconds = receive;

            if (null != _socket)
            {
                _socket.SetTimeoutSeconds(_sendTimeoutSeconds, _receiveTimeoutSeconds);
            }
        }

        /// <summary>
        /// 设置最大重连次数
        /// <param name="times">重连次数</param>
        /// </summary>
        public void SetMaxReconnectTimes(int times)
        {
            _maxReconnectTimes = times;
        }

        /// <summary>
        /// 监听数据接收回调
        /// <param name="callback">接收到的二进制数据回调</param>
        /// </summary>
        public void AddReceiveListener(EventCallBack<ReceiveEventInfo>.CALL_FUNC_EVENT callback)
        {
            if (null == _callbackReceiveData)
                _callbackReceiveData = new EventCallBack<ReceiveEventInfo>();
            _callbackReceiveData.AddCallBack(this, callback);
        }

        /// <summary>
        /// 移除数据接收回调
        /// <param name="callback">接收到的二进制数据回调</param>
        /// </summary>
        public void RemoveReceiveListener(EventCallBack<ReceiveEventInfo>.CALL_FUNC_EVENT callback)
        {
            if (null == _callbackReceiveData)
                return;
            _callbackReceiveData.RemoveCallBack(callback);
            if (0 == _callbackReceiveData.Count)
                _callbackReceiveData = null;
        }

        /// <summary>
        /// 监听数据发送结果回调
        /// <param name="callback">发送的结果回调(true:成功 false:失败)</param>
        /// </summary>
        public void AddSendResultListener(EventCallBack<SendEventInfo>.CALL_FUNC_EVENT callback)
        {
            if (null == _callbackSendDataResult)
                _callbackSendDataResult = new EventCallBack<SendEventInfo>();
            _callbackSendDataResult.AddCallBack(this, callback);
        }

        /// <summary>
        /// 移除数据发送结果回调
        /// <param name="callback">发送的结果回调(true:成功 false:失败)</param>
        /// </summary>
        public void RemoveSendResultListener(EventCallBack<SendEventInfo>.CALL_FUNC_EVENT callback)
        {
            if (null == _callbackSendDataResult)
                return;
            _callbackSendDataResult.RemoveCallBack(callback);
            if (0 == _callbackSendDataResult.Count)
                _callbackSendDataResult = null;
        }

        /// <summary>
        /// 监听重连回调
        /// <param name="callback">发送的结果回调(true:成功 false:失败)</param>
        /// </summary>
        public void AddReconnectListener(EventCallBack<System.Action<bool>>.CALL_FUNC_EVENT callback)
        {
            if (null == _callbackReconnect)
                _callbackReconnect = new EventCallBack<System.Action<bool>>();
            _callbackReconnect.AddCallBack(this, callback);
        }

        /// <summary>
        /// 移除重连回调
        /// <param name="callback">发送的结果回调(true:成功 false:失败)</param>
        /// </summary>
        public void RemoveReconnectListener(EventCallBack<System.Action<bool>>.CALL_FUNC_EVENT callback)
        {
            if (null == _callbackReconnect)
                return;
            _callbackReconnect.RemoveCallBack(callback);
            if (0 == _callbackReconnect.Count)
                _callbackReconnect = null;
        }

        /// <summary>
        /// 监听断线回调
        /// <param name="callback">断线回调</param>
        /// </summary>
        public void AddDisconnectListener(EventCallBack.CALL_FUNC_EVENT callback)
        {
            if (null == _callbackDisconnect)
                _callbackDisconnect = new EventCallBack();
            _callbackDisconnect.AddCallBack(this, callback);
        }

        /// <summary>
        /// 移除断线回调
        /// <param name="callback">断线回调</param>
        /// </summary>
        public void RemoveDisconnectListener(EventCallBack.CALL_FUNC_EVENT callback)
        {
            if (null == _callbackDisconnect)
                return;
            _callbackDisconnect.RemoveCallBack(callback);
            if (0 == _callbackDisconnect.Count)
                _callbackDisconnect = null;
        }

        /// <summary>
        /// 设置接收的信息头长度和解析回调
        /// <param name="信息头长度">headerLength</param>
        /// <param name="信息头解析回调，返回包体数据长度">callbackParse</param>
        /// </summary>
        public void SetHeaderParse(int headerLength, System.Func<byte[], IHeaderInfo> callbackParse)
        {
            _receiveHeaderLength = headerLength;
            _callbackReceiveHeaderParse = callbackParse;
        }

        /// <summary>
        /// 是否已成功连接
        /// </summary>
        public bool IsConnected()
        {
            return null != _socket && _socket.IsConnected();
        }

        private void StartReceiveData()
        {
#if UNITY_WEBGL
            WaitFor.Run(() =>
            {
                DoReceiveData();
                DoReceiveEvent();
                return !_isThreadReceiveStarted;
            }, () =>
            {

            });
#else
            WaitFor.Run(() =>
            {
                DoReceiveEvent();
                return !_isThreadReceiveStarted;
            }, () =>
            {

            });

            ThreadPool.RunThread(() =>
            {
                while (_isThreadReceiveStarted)
                {
                    DoReceiveData();
                    System.Threading.Thread.Sleep(10);
                }
            });
#endif
        }

        private void DoReceiveData()
        {
            if (null == _socket || !_socket.IsConnected() || !_socket.IsDataAvailable())
                return;

            if (null == _callbackReceiveHeaderParse || 0 >= _receiveHeaderLength)
            {
                Log.Error("ClientTCP DoReceiveData erorr: The header parsing method should be set first");
                return;
            }

            try
            {
                int receiveLength = _socket.Receive(_receiveBuffer, 0);
                if (receiveLength <= 0)
                {
                    SafeAddReceiveEventInfo(null, null, "ClientTCP DoReceiveData erorr: receive nothing");
                    _dataBuffer.Clear();
                }
                else
                {
                    _dataBuffer.Seek(0, System.IO.SeekOrigin.End);
                    _dataBuffer.WriteBytes(_receiveBuffer, 0, receiveLength);
                    _dataBuffer.Seek(0, System.IO.SeekOrigin.Begin);

                    //持续处理数据
                    while (_dataBuffer.GetLength() > 0)
                    {
                        //当DoParseReceiveData返回false时候表示数据不足，需要重新进行数据接收
                        if (!DoParseReceiveData())
                            break;
                    }
                }
            }
            catch (System.Exception e)
            {
                var errorMessage = "ClientTCP DoReceiveData exception: e=" + e + " data=" + _receiveBuffer.ToStringArray();
                SafeAddReceiveEventInfo(null, null, errorMessage);
                Log.Error(errorMessage);
            }
        }

        private bool DoParseReceiveData()
        {
            bool retValue = true;

            //获取信息头
            if (null == _currentReceiveHeaderInfo)
            {
                if (_dataBuffer.GetLength() >= _receiveHeaderLength)
                {
                    var headerData = _dataBuffer.ReadBytes(_receiveHeaderLength);
                    _currentReceiveHeaderInfo = _callbackReceiveHeaderParse(headerData);
                }
                //数据不足，需要继续接收数据
                else
                    retValue = false;
            }

            //获取信息体
            if (null != _currentReceiveHeaderInfo)
            {
                if (_dataBuffer.GetLength() >= _currentReceiveHeaderInfo.dataLength)
                {
                    var bodyData = _dataBuffer.ReadBytes(_currentReceiveHeaderInfo.dataLength);
                    _dataBuffer.ResetRemain();
                    SafeAddReceiveEventInfo(_currentReceiveHeaderInfo, bodyData, string.Empty);
                    _currentReceiveHeaderInfo = null;
                }
                //数据不足，需要继续接收数据
                else
                    retValue = false;
            }
            return retValue;
        }

        private void DoReceiveEvent()
        {
            if (null == _callbackReceiveData)
                return;

            lock (_queueReceiveEvents)
            {
                //同一桢是否需要这样把所有回调数据全部处理完毕？可能导致卡顿
                while (_queueReceiveEvents.Count > 0)
                {
                    var eventInfo = _queueReceiveEvents.Dequeue();
                    _callbackReceiveData.InvokeAllCallBack(eventInfo);
                    GameHelper.objectpool.RecyclingObject(eventInfo);
                }
            }
        }

        private void SafeAddReceiveEventInfo(IHeaderInfo header, byte[] body, string error)
        {
            if (null != _callbackReceiveData)
            {
                lock (_queueReceiveEvents)
                {
                    var newEventInfo = GameHelper.objectpool.Instantiate(() => new ReceiveEventInfo());
                    newEventInfo.header = header;
                    newEventInfo.body = body;
                    newEventInfo.error = error;
                    _queueReceiveEvents.Enqueue(newEventInfo);
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(error))
                    Log.Error("ClientTCP SafeAddReceiveEventInfo erorr: " + error);
            }
        }

        private void SafeInvokeSendEventInfo(byte[] data, string error)
        {
            if (null != _callbackSendDataResult)
            {
                var newEventInfo = GameHelper.objectpool.Instantiate(() => new SendEventInfo());
                newEventInfo.data = data;
                newEventInfo.error = error;
                _callbackSendDataResult.InvokeAllCallBack(this, newEventInfo);
                GameHelper.objectpool.RecyclingObject(newEventInfo);
            }
            else
            {
                if (!string.IsNullOrEmpty(error))
                    Log.Error("ClientTCP SafeInvoeSendEventInfo erorr: " + error);
            }
        }

        private void ConnectBase(string host, int port, System.Action<bool> callback)
        {
            if (null != _socket)
            {
                Disconnect();
            }

            _socket = GameHelper.socketHelper.CreateClientSocket();
            _socket.Connect(host, port, _connectTimeoutSeconds, (suceess) =>
            {
                if (suceess)
                {
                    _socket.SetTimeoutSeconds(_sendTimeoutSeconds, _receiveTimeoutSeconds);
                    if (null != callback)
                        callback(true);
                }
                else
                {
                    if (_currentReconnectTimes < _maxReconnectTimes)
                    {
                        ++_currentReconnectTimes;
                        Log.Info("ClientTCP ConnectBase reconnect start, current=" + _currentReconnectTimes + " total=" + _maxReconnectTimes);

                        if (null != _callbackReconnect)
                        {
                            ConnectBase(host, port, (sucessSub) =>
                            {
                                if (sucessSub)
                                {
                                    _callbackReconnect.InvokeAllCallBack((suceessCustom) =>
                                    {
                                        if (suceessCustom)
                                        {
                                            if (null != callback)
                                                callback(true);
                                        }
                                        else
                                        {
                                            ConnectBase(host, port, callback);
                                        }
                                    });
                                }
                                else if (_currentReconnectTimes >= _maxReconnectTimes)
                                {
                                    if (null != callback)
                                        callback(false);
                                }
                            });
                        }
                        else
                        {
                            ConnectBase(host, port, callback);
                        }
                    }
                    else
                    {
                        //到达最大重连次数，重连失败
                        Log.Info("ClientTCP ConnectBase reconnect out of max reconnect times=" + _maxReconnectTimes);
                        _socket = null;

                        if (null != callback)
                            callback(false);
                        _currentReconnectTimes = 0;
                    }
                }
            });
        }
    }
}